import copy
import random
import math
import time
import numpy as np
import re

# 定义常量


PI = 3.1415926
NUMIND = 25
LENIND = 10

crossPro = 0.9
mutaPro = 0.1
pop = None


class Indivi:
    def __init__(self):
        self.gene = [''] * 100  # 个体的二进制基因型字符串
        # self.gene2 = ''
        self.fitness = 0.0  # 个体的适应度值
        self.x = [0] * 100  # 个体对应的实数表现型
        # self.x2 = 0
        self.upLim = 0.0  # 个体的选择概率上界


class Popu:
    def __init__(self):
        self.indv = [Indivi() for _ in range(NUMIND)]  # 种群中的所有个体列表
        self.bestInd = Indivi()  # 种群中最优个体
        self.generan = 0  # 当前种群的迭代代数


pop = Popu()


def ranF(x, y):
    seed = time.time()
    random.seed(seed)
    return random.uniform(x, y)  # 生成指定范围内的随机浮点数


def F(x):
    return x * np.sin(10 * PI * x) + 2.0  # 要求解的函数


def fit(x, fs):
    if fs == '1':
        return 3 * (x[0] ** 2 - x[1]) ** 2
    elif fs == '2':
        return x[0] * math.sin(10 * PI * x[1]) + 2.0  # 计算目标函数的函数值
    else:
        result, num = convert_function_strings(fs, x)
        return eval(result)


def convert_function_strings(func_str, x):
    var_dict = {}
    for i, x_i in enumerate(x):
        var_dict[f'x{i + 1}'] = x_i

    def replace_var(match):
        var_name = match.group(1)
        if var_name not in var_dict:
            var_dict[var_name] = f'x[{len(var_dict)}]'
        return str(var_dict[var_name])

    def replace_func(match):
        func_name = match.group(1)
        arg = match.group(2)
        if func_name == "sin":
            return f"math.sin({arg})"
        elif func_name == "cos":
            return f"math.cos({arg})"
        else:
            return match.group(0)

    # Replace variables
    converted_str = re.sub(r'(\w\d+)', replace_var, func_str)
    # Replace functions
    converted_str = re.sub(r'(\w+)\(([^)]*)\)', replace_func, converted_str)

    var_count = len(var_dict) - len(x)
    return converted_str, var_count


def bin2dou(bp):
    retuV = 0
    for i in range(LENIND):
        tem = 0 if bp[i] == '0' else 1
        retuV = retuV + (tem * pow(2, LENIND - i - 1))  # 二进制转换为十进制浮点数
    return retuV


def bin2dou2(bp):
    retuV = 0
    for i in range(LENIND - 1):
        tem = 0 if bp[i] == '0' else 1
        retuV = retuV + (tem * pow(2, LENIND - i - 1))  # 二进制转换为十进制浮点数
    return retuV


def dou2ZD(x, lowBef, upBef, lowAft, upAft):
    return lowAft + (x - lowBef) * (upAft - lowAft) / (upBef - lowBef)  # 数值映射


def initPop():
    # 初始化种群，随机生成各个个体的基因型
    global pop
    pop.generan = 0

    for i in range(NUMIND):
        for k in range(LENIND):
            for n in range(x_dim):
                pop.indv[i].gene[n] += '1' if random.uniform(0, 1) > 0.5 else '0'
                # pop.indv[i].gene2 += '1' if random.uniform(0, 1) > 0.5 else '0'


def calFit():
    # 适应度计算
    global pop
    for i in range(NUMIND):
        for n in range(x_dim):
            pop.indv[i].x[n] = dou2ZD(bin2dou(pop.indv[i].gene[n]), 0, pow(2, LENIND) - 1, low, up)
            # pop.indv[i].x2 = dou2ZD(bin2dou(pop.indv[i].gene2), 0, pow(2, LENIND) - 1, low, up)
            pop.indv[i].fitness = fit(pop.indv[i].x, fs)


def calUp():
    global pop
    total_fitness = sum([ind.fitness for ind in pop.indv])  # 计算所有个体的适应度之和
    pop.indv[0].upLim = pop.indv[0].fitness / total_fitness  # 第一个个体的选择概率上界

    # 计算其他个体的选择概率上界
    for i in range(1, NUMIND):
        temp = pop.indv[i].fitness / total_fitness
        pop.indv[i].upLim = temp + pop.indv[i - 1].upLim


def HalfSear(value, LowBo, UpBo, InA):
    # 二分查找函数，用于在指定范围内查找一个数值的位置

    if LowBo >= UpBo:
        return UpBo
    Mid = (LowBo + UpBo) // 2
    if Mid == 0:
        return 0
    if value <= InA[Mid] and value > InA[Mid - 1]:
        return Mid
    elif value >= InA[Mid]:
        return HalfSear(value, Mid, UpBo, InA)
    else:
        return HalfSear(value, LowBo, Mid, InA)


def calSub():
    # 选择操作，根据个体的选择概率，生成下一代种群
    global pop
    poptem = Popu()
    TemFitn = [ind.upLim for ind in pop.indv]  # 记录所有个体的选择概率上界

    # 根据选择概率上界随机选取NUMIND个父代个体生成NUMIND个后代个体
    for i in range(NUMIND):
        rnd = ranF(0, 1)  # 随机数值
        NumTemp = HalfSear(rnd, 0, NUMIND, TemFitn)  # 查找随机数值所在的位置
        poptem.indv[i] = pop.indv[NumTemp]
    poptem.generan = pop.generan
    poptem.bestInd = pop.bestInd
    pop = poptem


def select():
    # 选择操作
    global pop
    calUp()  # 计算选择概率上界
    calSub()  # 生成下一代种群


def swithstr(s, t):
    # 交叉操作，用于交换两条染色体的一部分基因
    i = int(ranF(1, LENIND - 1))  # 随机生成交叉点
    s = list(s)
    t = list(t)
    for idx in range(i, LENIND):
        s[idx], t[idx] = t[idx], s[idx]  # 交换两条染色体在交叉点以后的基因
    return "".join(s), "".join(t)


def crossove():
    # 交叉操作，用于生成下一代个体
    global pop
    for _ in range(NUMIND // 2):
        if ranF(0, 1) > crossPro:  # 根据交叉概率决定是否进行交叉操作
            continue
        j = k = 0
        while j != k:
            j = int(ranF(0, NUMIND))
            k = int(ranF(0, NUMIND))
            for n in range(x_dim):
                pop.indv[j].gene[n], pop.indv[k].gene[n] = swithstr(pop.indv[j].gene[n], pop.indv[k].gene[n])  # 交叉两条染色体
                # pop.indv[j].gene2, pop.indv[k].gene2 = swithstr(pop.indv[j].gene2, pop.indv[k].gene2)  # 交叉两条染色体


def changeM(ind):
    # 变异操作，将个体的某个基因取反
    r = int(ranF(0, LENIND))  # 随机生成变异位置
    ind = list(ind)
    ind[r] = '1' if ind[r] == '0' else '0'  # 将变异位置上的基因取反
    return "".join(ind)


def mutation():
    # 变异操作，用于生成下一代个体
    global pop
    for i in range(NUMIND):
        if ranF(0, 1) > mutaPro:  # 根据变异概率决定是否进行变异操作
            continue
        else:
            for n in range(x_dim):
                pop.indv[i].gene[n] = changeM(pop.indv[i].gene[n])  # 变异个体的染色体
                # pop.indv[i].gene2 = changeM(pop.indv[i].gene2)


def max_individual():
    # 查找种群中适应度最高的个体
    global pop
    max_idx = 0
    max_fitness = pop.indv[0].fitness
    for i in range(1, NUMIND):
        if pop.indv[i].fitness > max_fitness:
            max_fitness = pop.indv[i].fitness
            max_idx = i
    pop.bestInd = copy.deepcopy(pop.indv[max_idx])


# x = np.linspace(-2, 2, num=1000)
# plt.ion()
# plt.plot(x, F(x), c='blue')
def decimal_to_binary(decimal, num_bits):
    binary = ''
    while decimal > 0:
        binary = str(decimal % 2) + binary
        decimal = decimal // 2
    if len(binary) < num_bits:
        binary = '0' * (num_bits - len(binary)) + binary
    return binary


def change_gene():
    global LENIND
    LENIND = LENIND + 1
    for i in range(NUMIND):
        for n in range(x_dim):
            data = bin2dou2(pop.indv[i].gene[n])
            pop.indv[i].gene[n] = '0' + str(pop.indv[i].gene[n])
            if len(pop.indv[i].gene[n]) > LENIND:
                pop.indv[i].gene[n] = pop.indv[i].gene[n][-LENIND:]
            pop.indv[i].gene[n] = decimal_to_binary(data, LENIND)


def main(fs1, low1, up1, x_dim1):
    global fs
    fs = fs1
    global up
    up = up1
    global low
    low = low1
    global x_dim
    x_dim = x_dim1
    global pop
    pop = Popu()  # 初始化种群

    initPop()  # 随机初始化种群中的个体
    now_best = 0.
    file_path = ''
    num = 1
    if fs == '1':
        file_path = "fun1_decimal.txt"
    elif fs == '2':
        file_path = "fun2_decimal.txt"
    else:
        file_path = 'others_decimal.txt'
    with open(file_path, "w") as fp:
        while pop.generan < 10000:
            # 当最优解不超过目标值且迭代次数不超过上限时进行遗传操作
            calFit()  # 计算所有个体的适应度
            max_individual()  # 查找适应度最高的个体
            pop.generan += 1
            print(f"The generation is {pop.generan}, the best fitness is {pop.bestInd.fitness}")
            fp.write(f"Best Individual :{pop.bestInd.gene}, {pop.bestInd.fitness}\n")  # 将结果写入文件
            select()  # 选择操作，包括计算选择概率上界和生成下一代种群
            crossove()  # 交叉操作，用于生成下一代个体
            if pop.generan == 100:  # 在第100代时，将变异概率设置为0.3
                global mutaPro
                mutaPro = 0.3
                print("The mutation probability is set to 0.3")
            mutation()  # 变异操作
            pop.indv[0] = pop.bestInd  # 将最优个体保留到下一代
            if pop.bestInd.fitness > now_best:
                now_best = pop.bestInd.fitness
            fp.write(f"Final Best Fitness :{now_best}\n")
            if pop.bestInd.fitness == now_best:
                num += 1
            else:
                num = 1
            if num == 300:
                change_gene()
                print(LENIND)
                num = 1
    print("Finished\n\n")


if __name__ == "__main__":
    main('1', -10, 10, 2)
# main('3 * (x1 ** 2 - x2) ** 2+x3', -1, 2, 1)

